package com.common.utils;

import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 深拷贝工具
 */
public class DeepCopyUtils {
    /**
     * 深拷贝对象
     *
     * @param source            源对象
     * @param clazz             目标对象的类型
     * @param excludeProperties 不需要拷贝的属性名
     * @return 拷贝后的对象
     */
    public static <T> T deepCopy(Object source, Class<T> clazz, String... excludeProperties) {
        T target = null;
        try {
            //创建了一个目标对象 target
            target = clazz.getDeclaredConstructor().newInstance();
            //初始化了一个 HashMap 对象 map，用于存储已经深拷贝的源对象和目标对象的映射关系
            Map<Object, Object> map = new HashMap<>();
            deepCopyIterative(source, target, map, excludeProperties);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            e.printStackTrace();
        }
        return target;
    }

    /**
     * 深拷贝对象
     *
     * @param source            源对象
     * @param target            目标对象的类型
     * @param map               存储已经拷贝过的对象的Map
     * @param excludeProperties 不需要拷贝的属性名
     * @return 拷贝后的对象
     */
    private static void deepCopyIterative(Object source, Object target, Map<Object, Object> map, String... excludeProperties) {
        if (source == null || target == null) {
            return;
        }

        PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(source.getClass());
        for (PropertyDescriptor sourcePd : sourcePds) {
            if (sourcePd.getReadMethod() != null && !"class".equals(sourcePd.getName())) {
                PropertyDescriptor targetPd = BeanUtils.getPropertyDescriptor(target.getClass(), sourcePd.getName());
                if (targetPd != null && targetPd.getWriteMethod() != null) {
                    String propertyName = sourcePd.getName();
                    if (Arrays.asList(excludeProperties).contains(propertyName)) {
                        continue;
                    }
                    try {
                        Object sourceValue = sourcePd.getReadMethod().invoke(source);
                        if (sourceValue != null) {
                            targetPd.getWriteMethod().invoke(target, sourceValue);
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

}
